#pragma once

#include "exceptions.h"

namespace PS
{
template <class DerivedIterator, class AccessorType>
class LinearIndexIterator
{
public:
  /**
   * Iterator category.
   */
  using iterator_category = std::random_access_iterator_tag;

  /**
   * An alias for the type you get when you dereference an iterator of the
   * current kind.
   */
  using value_type = AccessorType;

  /**
   * Difference type.
   */
  using difference_type = std::ptrdiff_t;

  /**
   * Reference type.
   */
  using reference = const value_type &;

  /**
   * Pointer type.
   */
  using pointer = const value_type *;

  /**
   * Size type used by the underlying container.
   */
  using size_type = typename value_type::size_type;

  /**
   * Copy operator.
   */
  DerivedIterator &
  operator=(const DerivedIterator &it);

  /**
   * Prefix increment.
   */
  DerivedIterator &
  operator++();

  /**
   * Postfix increment.
   */
  DerivedIterator
  operator++(int);

  /**
   * Prefix decrement.
   */
  DerivedIterator &
  operator--();

  /**
   * Postfix decrement.
   */
  DerivedIterator
  operator--(int);

  /**
   * Return an iterator that is @p n entries ahead of the current one.
   */
  DerivedIterator
  operator+(const difference_type n) const;

  /**
   * Return an iterator that is @p n entries behind the current one.
   */
  DerivedIterator
  operator-(const difference_type n) const;

  /**
   * Increment the iterator position by @p n.
   */
  DerivedIterator &
  operator+=(const difference_type n);

  /**
   * Decrement the iterator position by @p n.
   */
  DerivedIterator &
  operator-=(const difference_type n);

  /**
   * Return the distance between the current iterator and the argument. The
   * distance is given by how many times one has to apply operator++() to the
   * current iterator to get the argument (for a positive return value), or
   * operator--() (for a negative return value).
   */
  difference_type
  operator-(const DerivedIterator &p) const;

  /**
   * Dereferencing operator.
   */
  reference
  operator*() const;

  /**
   * Dereferencing operator.
   */
  pointer
  operator->() const;

  /**
   * Comparison operator. Returns <code>true</code> if both iterators point to
   * the same entry in the same container.
   */
  template <typename OtherIterator>
  friend typename std::enable_if<
    std::is_convertible<OtherIterator, DerivedIterator>::value,
    bool>::type
  operator==(const LinearIndexIterator &left, const OtherIterator &right)
  {
    const auto &right_2 = static_cast<const DerivedIterator &>(right);
    return left.accessor == right_2.accessor;
  }

  /**
   * Opposite of operator==().
   */
  template <typename OtherIterator>
  friend typename std::enable_if<
    std::is_convertible<OtherIterator, DerivedIterator>::value,
    bool>::type
  operator!=(const LinearIndexIterator &left, const OtherIterator &right)
  {
    return !(left == right);
  }

  /**
   * Comparison operator: uses the same ordering as operator<(), but also
   * checks for equality.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool
  operator<=(const DerivedIterator &) const;

  /**
   * Comparison operator: uses the same ordering as operator>(), but also
   * checks for equality.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool
  operator>=(const DerivedIterator &) const;

  /**
   * Comparison operator. Result is true if either the first row number is
   * smaller or if the row numbers are equal and the first index is smaller.
   *
   * This function is only valid if both iterators point into the same
   * container.
   */
  bool
  operator<(const DerivedIterator &) const;

  /**
   * Comparison operator. Works in the same way as operator<(), just the other
   * way round.
   */
  bool
  operator>(const DerivedIterator &) const;

protected:
  /*
   * The inheriting class should have a default constructor.
   */
  LinearIndexIterator() = default; // NOLINT

  /**
   * Constructor that copies an accessor.
   */
  LinearIndexIterator(const AccessorType accessor);

protected:
  /**
   * Store an object of the accessor class.
   */
  AccessorType accessor;
};



template <class DerivedIterator, class AccessorType>
inline DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator=(
  const DerivedIterator &it)
{
  accessor.container    = it.container;
  accessor.linear_index = it.linear_index;
  return static_cast<DerivedIterator &>(*this);
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator++()
{
  return operator+=(1);
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator++(int)
{
  const DerivedIterator copy(this->accessor);
                        operator+=(1);
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator--()
{
  return operator+=(-1);
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator--(int)
{
  const DerivedIterator copy(this->accessor);
                        operator+=(-1);
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator+(
  const difference_type n) const
{
  DerivedIterator copy(this->accessor);
  copy += n;
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator
LinearIndexIterator<DerivedIterator, AccessorType>::operator-(
  const difference_type n) const
{
  DerivedIterator copy(this->accessor);
  copy += -n;
  return copy;
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator+=(
  const difference_type n)
{
  accessor.linear_index += n;
  return static_cast<DerivedIterator &>(*this);
}



template <class DerivedIterator, class AccessorType>
inline DerivedIterator &
LinearIndexIterator<DerivedIterator, AccessorType>::operator-=(
  const difference_type n)
{
  return operator+=(-n);
}



template <class DerivedIterator, class AccessorType>
inline
  typename LinearIndexIterator<DerivedIterator, AccessorType>::difference_type
  LinearIndexIterator<DerivedIterator, AccessorType>::operator-(
    const DerivedIterator &other) const
{
  Assert(this->accessor.container == other.accessor.container,
         ExcMessage(
           "Only iterators pointing to the same container can be compared."));
  return this->accessor.linear_index - other.accessor.linear_index;
}



template <class DerivedIterator, class AccessorType>
inline typename LinearIndexIterator<DerivedIterator, AccessorType>::reference
LinearIndexIterator<DerivedIterator, AccessorType>::operator*() const
{
  return accessor;
}



template <class DerivedIterator, class AccessorType>
inline typename LinearIndexIterator<DerivedIterator, AccessorType>::pointer
LinearIndexIterator<DerivedIterator, AccessorType>::operator->() const
{
  return &accessor;
}



template <class DerivedIterator, class AccessorType>
inline bool
LinearIndexIterator<DerivedIterator, AccessorType>::operator<=(
  const DerivedIterator &other) const
{
  return (*this == other) || (*this < other);
}



template <class DerivedIterator, class AccessorType>
inline bool
LinearIndexIterator<DerivedIterator, AccessorType>::operator>=(
  const DerivedIterator &other) const
{
  return !(*this < other);
}



template <class DerivedIterator, class AccessorType>
inline bool
LinearIndexIterator<DerivedIterator, AccessorType>::operator<(
  const DerivedIterator &other) const
{
  Assert(this->accessor.container == other.accessor.container,
         ExcMessage(
           "Only iterators pointing to the same container can be compared."));
  return this->accessor.linear_index < other.accessor.linear_index;
}



template <class DerivedIterator, class AccessorType>
inline bool
LinearIndexIterator<DerivedIterator, AccessorType>::operator>(
  const DerivedIterator &other) const
{
  return other < static_cast<const DerivedIterator &>(*this);
}



template <class DerivedIterator, class AccessorType>
inline LinearIndexIterator<DerivedIterator, AccessorType>::LinearIndexIterator(
  const AccessorType accessor)
  : accessor(accessor)
{}

} // namespace PS